﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using WillsLLC.DbGen.Text.Inflection;

namespace WillsLLC.DbGen.Text
{
    /// <summary>
    /// A set of extension methods that contain logic for pluralizing English words.
    /// </summary>
    public static class PluralizationExtensions
    {
        public static String Pluralize(this String wordOrPhrase)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, null, 2, false, out resultRule);
        }

        public static String Pluralize(this String wordOrPhrase, String pluralSuffix)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, pluralSuffix, 2, false, out resultRule);
        }

        public static String Pluralize(this String wordOrPhrase, Int64 count)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, null, count, false, out resultRule);
        }

        public static String Pluralize(this String wordOrPhrase, String pluralSuffix, Int64 count)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, pluralSuffix, count, false, out resultRule);
        }

        public static String PluralizeClassical(this String wordOrPhrase)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, null, 2, true, out resultRule);
        }

        public static String PluralizeClassical(this String wordOrPhrase, String pluralSuffix)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, pluralSuffix, 2, true, out resultRule);
        }

        public static String PluralizeClassical(this String wordOrPhrase, String pluralSuffix, Int64 count)
        {
            PluralizationRule resultRule;
            return wordOrPhrase.Pluralize(null, pluralSuffix, count, true, out resultRule);
        }

        public static String Pluralize(this String wordOrPhrase, String singularSuffix, String pluralSuffix, Int64 count, Boolean classicalMode, out PluralizationRule resultRule)
        {
            resultRule = PluralizationRule.NonPluralized;

            if (count == 1) return wordOrPhrase;

            String word = null;
            String phrase = null;

            Match phraseMatch = InflectionUtility.GetPhrasePattern().Match(wordOrPhrase);
            if (phraseMatch.Success)
            {
                phrase = phraseMatch.Groups[1].Value;
                word = phraseMatch.Groups[2].Value;
            }
            else word = wordOrPhrase;

            if (singularSuffix == null)
            {
                singularSuffix = InflectionUtility.SingularSuffixes.Where(suffix => word.EndsWithSuffix(suffix.Value)).Select(suffix => suffix.Value).FirstOrDefault();
            }
            else if (InflectionUtility.SingularSuffixes.ContainsKey(singularSuffix))
            {
                singularSuffix = InflectionUtility.SingularSuffixes[singularSuffix];
            }
            else
            {
                singularSuffix = String.Empty;
            }

            // Plural nouns, step 1, user-defined
            if (pluralSuffix != null)
            {
                resultRule = PluralizationRule.UserDefined;
                return word.Inflect(singularSuffix, pluralSuffix);
            }

            // Plural nouns, step 2
            if (InflectionUtility.DoesNotInflectPlural.Count(doesNotInflect => InflectionUtility.GetSuffixPattern(doesNotInflect).IsMatch(word)) > 0
                || word.IsCategoryMember(String.Empty, String.Empty, classicalMode))
            {
                resultRule = PluralizationRule.DoesNotInflectPlural;
                return word;
            }

            // Plural nouns, step 3, pronouns
            String pluralPronoun = InflectionUtility.Pronouns.Select(pronoun => pronoun.GetPlural(word)).FirstOrDefault(plural => plural != null);
            if (pluralPronoun != null)
            {
                resultRule = PluralizationRule.Pronoun;
                return pluralPronoun;
            }

            // Plural nouns, step 4, irregular plurals
            String irregularPluralNoun = InflectionUtility.IrregularPlurals.Select(irregular => irregular.GetPlural(word, classicalMode)).FirstOrDefault(plural => plural != null);
            if (irregularPluralNoun != null)
            {
                resultRule = PluralizationRule.IrregularPlural;
                return irregularPluralNoun;
            }

            // Plural nouns, step 5, irregular inflections
            String irregularInflection = InflectionUtility.IrregularInflections.Select(inflection => inflection.GetPlural(word)).SingleOrDefault(plural => plural != null);
            if (irregularInflection != null)
            {
                resultRule = PluralizationRule.IrregularInflection;
                return irregularInflection;
            }

            // Plural nouns, step 6, assimilated classic inflections
            String assimilatedClassic;
            if(word.TryGetCategorizedInflection(classicalMode, out assimilatedClassic,
                new SingularPluralSuffixPair("ix", "ixes"),
                new SingularPluralSuffixPair("ex", "ices"),
                new SingularPluralSuffixPair("um", "a"),
                new SingularPluralSuffixPair("on", "a"),
                new SingularPluralSuffixPair("a", "ae")))
            {
                resultRule = PluralizationRule.AssimilatedClassicalInflection;
                return assimilatedClassic;
            }

            // Plural nouns, step 7, classical variants
            if (classicalMode)
            {
                String classicalVariant;
                if (word.TryGetSuffixInflection(out classicalVariant,
                    new SingularPluralSuffixPair("trix", "trices"),
                    new SingularPluralSuffixPair("eau", "eaux"),
                    new SingularPluralSuffixPair("ieu", "ieux"),
                    new SingularPluralSuffixPair("[iay]nx", "nx", "nges")))
                {
                    resultRule = PluralizationRule.ClassicalVariantOfModernInflection;
                    return classicalVariant;
                }
                else if (word.TryGetCategorizedInflection(classicalMode, out classicalVariant,
                    new SingularPluralSuffixPair("en", "ina"),
                    new SingularPluralSuffixPair("a", "ata"),
                    new SingularPluralSuffixPair("is", "ides"),
                    new SingularPluralSuffixPair("us", "i"),
                    new SingularPluralSuffixPair("us", String.Empty, String.Empty),
                    new SingularPluralSuffixPair("o", "i"),
                    new SingularPluralSuffixPair(String.Empty, "i"),
                    new SingularPluralSuffixPair(String.Empty, "im")))
                {
                    resultRule = PluralizationRule.ClassicalVariantOfModernInflection;
                    return classicalVariant;
                }
            }

            // Plural nouns, step 8, ch, sh, ss all take es
            String esShSsToEs;
            if (word.TryGetSuffixInflection(out esShSsToEs,
                new SingularPluralSuffixPair("[cs]h", "h", "hes"),
                new SingularPluralSuffixPair("ss", "sses")))
            {
                resultRule = PluralizationRule.Ch_Sh_Ss_to_Es;
                return esShSsToEs;
            }

            // Plural nouns, step 9, f, fe take ves                                    
            String fFeToVes;
            if (word.TryGetSuffixInflection(out fFeToVes,
                new SingularPluralSuffixPair("[aeo]lf", "f", "ves"),
                new SingularPluralSuffixPair("[^d]lf", "f", "ves"),
                new SingularPluralSuffixPair("arf", "f", "ves"),
                new SingularPluralSuffixPair("[nlw]ife", "fe", "ves")))
            {
                resultRule = PluralizationRule.F_Fe_to_Ves;
                return fFeToVes;
            }

            // Plural nouns, step 10, vowel-y to ys
            String vowelYtoYs;
            if (word.TryGetSuffixInflection(out vowelYtoYs,
                new SingularPluralSuffixPair("[aeiou]y", "y", "ys"),
                new SingularPluralSuffixPair("[A-Z].y", "y", "ys"),
                new SingularPluralSuffixPair("y", "ies")))
            {
                resultRule = PluralizationRule.Vowel_Y_to_Ys;
                return vowelYtoYs;
            }

            // Plural nouns, step 11, o to os
            String oToOs;
            if (word.TryGetCategorizedInflection(classicalMode, out oToOs,
                new SingularPluralSuffixPair("o", "os")))
            {
                resultRule = PluralizationRule.O_to_Os;
                return oToOs;
            }
            else if (word.TryGetSuffixInflection(out oToOs,
                new SingularPluralSuffixPair("[aeiou]o", "o", "os"),
                new SingularPluralSuffixPair("o", "oes")))
            {
                resultRule = PluralizationRule.O_to_Os;
                return oToOs;
            }

            // Plural nouns, step 12, s
            return word.Inflect(String.Empty, "s");
        }

        public static String Depluralize(this String word, Boolean classicalMode, out PluralizationRule resultRule)
        {
            resultRule = PluralizationRule.NonPluralized;

            // Plural nouns, step 2
            if (InflectionUtility.DoesNotInflectPlural.Count(doesNotInflect => InflectionUtility.GetSuffixPattern(doesNotInflect).IsMatch(word)) > 0
                || word.IsCategoryMember(String.Empty, String.Empty, classicalMode))
            {
                resultRule = PluralizationRule.DoesNotInflectPlural;
                return word;
            }

            // Plural nouns, step 3, pronouns
            String singularPronoun = InflectionUtility.Pronouns.Where(pronoun => pronoun.Plural.Equals(word, StringComparison.InvariantCultureIgnoreCase)).Select(pronoun => pronoun.Singular).SingleOrDefault();
            if (singularPronoun != null)
            {
                resultRule = PluralizationRule.Pronoun;
                return singularPronoun;
            }

            // Plural nouns, step 4, irregular plurals
            String irregularSingular = InflectionUtility.IrregularPlurals
                .Where(irregular => classicalMode ? irregular.PluralClassical.Equals(word, StringComparison.InvariantCultureIgnoreCase) : irregular.PluralAnglicized.Equals(word, StringComparison.InvariantCultureIgnoreCase))
                .Select(irregular => irregular.Singular).SingleOrDefault();
            if (irregularSingular != null)
            {
                resultRule = PluralizationRule.IrregularPlural;
                return irregularSingular;
            }

            // Plural nouns, step 5, irregular inflections
            String irregularInflection = InflectionUtility.IrregularInflections
                .Where(irregular => irregular.PluralSuffix
                .Select(inflection => inflection.GetPlural(word)).SingleOrDefault(plural => plural != null);
            if (irregularInflection != null)
            {
                resultRule = PluralizationRule.IrregularInflection;
                return irregularInflection;
            }

            // Plural nouns, step 6, assimilated classic inflections
            String assimilatedClassic;
            if (word.TryGetCategorizedInflection(classicalMode, out assimilatedClassic,
                new SingularPluralSuffixPair("ix", "ixes"),
                new SingularPluralSuffixPair("ex", "ices"),
                new SingularPluralSuffixPair("um", "a"),
                new SingularPluralSuffixPair("on", "a"),
                new SingularPluralSuffixPair("a", "ae")))
            {
                resultRule = PluralizationRule.AssimilatedClassicalInflection;
                return assimilatedClassic;
            }

            // Plural nouns, step 7, classical variants
            if (classicalMode)
            {
                String classicalVariant;
                if (word.TryGetSuffixInflection(out classicalVariant,
                    new SingularPluralSuffixPair("trix", "trices"),
                    new SingularPluralSuffixPair("eau", "eaux"),
                    new SingularPluralSuffixPair("ieu", "ieux"),
                    new SingularPluralSuffixPair("[iay]nx", "nx", "nges")))
                {
                    resultRule = PluralizationRule.ClassicalVariantOfModernInflection;
                    return classicalVariant;
                }
                else if (word.TryGetCategorizedInflection(classicalMode, out classicalVariant,
                    new SingularPluralSuffixPair("en", "ina"),
                    new SingularPluralSuffixPair("a", "ata"),
                    new SingularPluralSuffixPair("is", "ides"),
                    new SingularPluralSuffixPair("us", "i"),
                    new SingularPluralSuffixPair("us", String.Empty, String.Empty),
                    new SingularPluralSuffixPair("o", "i"),
                    new SingularPluralSuffixPair(String.Empty, "i"),
                    new SingularPluralSuffixPair(String.Empty, "im")))
                {
                    resultRule = PluralizationRule.ClassicalVariantOfModernInflection;
                    return classicalVariant;
                }
            }

            // Plural nouns, step 8, ch, sh, ss all take es
            String esShSsToEs;
            if (word.TryGetSuffixInflection(out esShSsToEs,
                new SingularPluralSuffixPair("[cs]h", "h", "hes"),
                new SingularPluralSuffixPair("ss", "sses")))
            {
                resultRule = PluralizationRule.Ch_Sh_Ss_to_Es;
                return esShSsToEs;
            }

            // Plural nouns, step 9, f, fe take ves                                    
            String fFeToVes;
            if (word.TryGetSuffixInflection(out fFeToVes,
                new SingularPluralSuffixPair("[aeo]lf", "f", "ves"),
                new SingularPluralSuffixPair("[^d]lf", "f", "ves"),
                new SingularPluralSuffixPair("arf", "f", "ves"),
                new SingularPluralSuffixPair("[nlw]ife", "fe", "ves")))
            {
                resultRule = PluralizationRule.F_Fe_to_Ves;
                return fFeToVes;
            }

            // Plural nouns, step 10, vowel-y to ys
            String vowelYtoYs;
            if (word.TryGetSuffixInflection(out vowelYtoYs,
                new SingularPluralSuffixPair("[aeiou]y", "y", "ys"),
                new SingularPluralSuffixPair("[A-Z].y", "y", "ys"),
                new SingularPluralSuffixPair("y", "ies")))
            {
                resultRule = PluralizationRule.Vowel_Y_to_Ys;
                return vowelYtoYs;
            }

            // Plural nouns, step 11, o to os
            String oToOs;
            if (word.TryGetCategorizedInflection(classicalMode, out oToOs,
                new SingularPluralSuffixPair("o", "os")))
            {
                resultRule = PluralizationRule.O_to_Os;
                return oToOs;
            }
            else if (word.TryGetSuffixInflection(out oToOs,
                new SingularPluralSuffixPair("[aeiou]o", "o", "os"),
                new SingularPluralSuffixPair("o", "oes")))
            {
                resultRule = PluralizationRule.O_to_Os;
                return oToOs;
            }

            // Plural nouns, step 12, s
            return word.Inflect(String.Empty, "s");
        }
    }
}
